#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model.Errors;

#endregion

namespace Weazel.Gezel.Model
{
    /// <summary>
    /// Extends the Model component by providing verification capabilities.
    /// </summary>
    public partial class Model : IVerifiable
    {
        /// <summary>
        /// True iff model was successfully verified
        /// </summary>
        private bool isVerified;
        public bool IsVerified
        {
            get { return isVerified; }
        }

        /// <summary>
        /// Convenience method which creates the
        /// error list, calls the verification
        /// interface method and returns the 
        /// verification results
        /// </summary>
        /// <returns></returns>
        public bool Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            errors = new List<VerificationError>();
            warnings = new List<VerificationWarning>();

            // null is necessary to match the other Verify method
            Verify(ref errors, ref warnings, null);

            return errors.Count != 0;
        }

        /// <summary>
        /// The model verifies its contents by simply
        /// delegating the verification steps to its
        /// sub compontents.
        /// </summary>
        /// <param name="errors"></param>
        public void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
        {
            //
            // Verify options
            //
            foreach (OptionDirective option in optionDirectives)
            {
                option.Verify(ref errors, ref warnings);
            }

            //
            // Verify constants
            //
            foreach (LeafExpression constant in constants.Values)
            {
                constant.Verify(ref errors, ref warnings);
            }

            //
            // Verify datapaths
            //
            VerifyDatapaths(ref errors, ref warnings);

            //
            // Check if model is empty
            //
            if (ipBlocks.Count == 0 && controllers.Count == 0 && datapaths.Count == 0)
            {
                ErrorMessage message = new ErrorMessage("Empty design");
                errors.Add(new VerificationError(VerificationErrorType.EmptyDesign, message));
            }

            //
            // Verify ipblocks
            //
            foreach (IPBlock ipBlock in ipBlocks.Values)
            {
                ipBlock.Verify(ref errors, ref warnings);
            }

            //
            // Verify controllers
            //
            foreach (Controller controller in controllers.Values)
            {
                controller.Verify(ref errors, ref warnings);
            }

            //
            // Verify systems..
            //
            foreach (system system in systems.Values)
            {
                system.Verify(ref errors, ref warnings);
            }

            // if no errors was found, mark
            // model as successfully verified
            if (errors.Count == 0)
                isVerified = true;
        }

        private Dictionary<string, Datapath> templates = new Dictionary<string, Datapath>();


        private void VerifyDatapaths(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {            
            // build hash to lookup controller of datapath
            Dictionary<string, Controller> dpToController = new Dictionary<string, Controller>();
            foreach (Controller ctrl in controllers.Values)
            {
                // check if datapath reference is null. programmer's check.
                if (ctrl.DatapathRef == null)
                {
                    string message =
                        string.Format(
                            "'DatapathRef' not set for controller '{0}'.",
                            ctrl.Name
                            );

                    throw new NullReferenceException(message);
                }

                dpToController[ctrl.DatapathRef] = ctrl;
            }

            // we cant use the foreach loop to iterate
            // directly over the datapaths when modifying
            // them, therefore we copy keys to array first
            string[] keys = new string[datapaths.Count];
            datapaths.Keys.CopyTo(keys, 0);

            foreach (string key in keys)
            {
                Datapath instance = datapaths[key];

                if (instance.ClonedFromDatapath != null)
                {
                    if (datapaths.ContainsKey(instance.ClonedFromDatapath))
                    {
                        datapaths[key] = datapaths[instance.ClonedFromDatapath].Clone(instance.LocalName);

                        // retain the name of the datapath from which the clone was instanced
                        datapaths[key].ClonedFromDatapath = instance.ClonedFromDatapath;

                        if (dpToController.ContainsKey(instance.ClonedFromDatapath))
                        {
                            Controller ctrl = dpToController[instance.ClonedFromDatapath].Clone(instance.LocalName);
                            controllers[ctrl.LocalName + "_" + instance.LocalName] = ctrl;
                        }
                        else
                        {
                            ErrorMessage message = new ErrorMessage("No controller associated with datapath '");
                            message.AddEntity(datapaths[key]);
                            message.AddString("' at '");
                            message.AddPosition(datapaths[key].StartPosition);

                            errors.Add(new VerificationError(VerificationErrorType.DatapathNoController, message));
                        }
                    }
                    else
                    {
                        // the datapath could not be found
                        ErrorMessage message = new ErrorMessage("Unknown datapath '");
                        message.AddString(instance.ClonedFromDatapath);
                        message.AddString("' at '");
                        message.AddPosition(instance.StartPosition);

                        errors.Add(new VerificationError(VerificationErrorType.CloneUnknownDatapath, message));
                    }
                }

                // datapaths[key].Verify(ref errors, ref warnings);
            }

            foreach (string key in keys)
                datapaths[key].Verify(ref errors, ref warnings);

            // Find out if all dp's has an associated controller

            // initialize a set containing the datapaths of the design
            List<Datapath> datapathsToCheck = new List<Datapath>();
            foreach (Datapath dp in datapaths.Values)
                datapathsToCheck.Add(dp);

            // remove all datapaths that has an associated controller (if they exist!)
            foreach (Controller ctrl in controllers.Values)
                if (datapaths.ContainsKey(ctrl.DatapathRef))
                    datapathsToCheck.Remove(datapaths[ctrl.DatapathRef]);

            // if any dp's remain in the set they have no controller..
            if (datapathsToCheck.Count > 0)
            {
                foreach (Datapath dp in datapathsToCheck)
                {
                    ErrorMessage message = new ErrorMessage("No controller associated with datapath '");
                    message.AddEntity(dp);
                    message.AddString("' at '");
                    message.AddPosition(dp.StartPosition);

                    errors.Add(new VerificationError(VerificationErrorType.DatapathNoController, message));
                }
            }
        }
    }
}